PooledHiLoSequencePersister.java
package org.codefilarete.stalactite.mapping.id.sequence.hilo;
import java.sql.Connection;
import java.util.Map;
import org.codefilarete.stalactite.engine.runtime.BeanPersister;
import org.codefilarete.stalactite.mapping.DefaultEntityMapping;
import org.codefilarete.stalactite.mapping.id.manager.AlreadyAssignedIdentifierManager;
import org.codefilarete.tool.collection.Maps;
import org.codefilarete.reflection.PropertyAccessor;
import org.codefilarete.stalactite.engine.SeparateTransactionExecutor;
import org.codefilarete.stalactite.mapping.id.sequence.hilo.PooledHiLoSequencePersister.Sequence;
import org.codefilarete.stalactite.mapping.id.sequence.hilo.PooledHiLoSequencePersister.SequenceTable;
import org.codefilarete.stalactite.sql.ConnectionConfiguration.ConnectionConfigurationSupport;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Database.Schema;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
/**
* Persister dedicated to {@link Sequence}.
*
* The same instance can be shared, as long as each calls {@link #reservePool(String, int)} with a different name parameter to avoid
* sequence name collision.
*
* @author Guillaume Mary
*/
public class PooledHiLoSequencePersister extends BeanPersister<Sequence, String, SequenceTable> {
/**
* Constructor with default table and column names.
* @param dialect the {@link Dialect} to use for database dialog
* @param separateTransactionExecutor a transaction provider that must give a new and separate transaction
* @param jdbcBatchSize the JDBC batch size, not really useful for this class since it doesn't do massive insert
* @see PooledHiLoSequenceStorageOptions#DEFAULT
*/
public PooledHiLoSequencePersister(Dialect dialect, SeparateTransactionExecutor separateTransactionExecutor, int jdbcBatchSize) {
this(PooledHiLoSequenceStorageOptions.DEFAULT, dialect, separateTransactionExecutor, jdbcBatchSize);
}
public PooledHiLoSequencePersister(PooledHiLoSequenceStorageOptions storageOptions, Dialect dialect, SeparateTransactionExecutor separateTransactionExecutor, int jdbcBatchSize) {
// we reuse default PersistentContext
super(new SequencePersisterConfigurer().buildConfiguration(storageOptions),
dialect, new ConnectionConfigurationSupport(separateTransactionExecutor, jdbcBatchSize));
}
public long reservePool(String sequenceName, int poolSize) {
SequenceBoundJdbcOperation jdbcOperation = new SequenceBoundJdbcOperation(sequenceName, poolSize);
// the operation is executed in a new and parallel transaction in order to manage concurrent accesses
((SeparateTransactionExecutor) getConnectionProvider()).executeInNewTransaction(jdbcOperation);
return jdbcOperation.getUpperBound();
}
public static class SequenceTable extends Table<SequenceTable> {
private final Column<SequenceTable, Long> nextValColumn;
private final Column<SequenceTable, String> sequenceNameColumn;
public SequenceTable(Schema schema, String name, String sequenceNameColName, String nextValColName) {
super(schema, name);
sequenceNameColumn = addColumn(sequenceNameColName, String.class);
sequenceNameColumn.setPrimaryKey(true);
nextValColumn = addColumn(nextValColName, long.class);
}
public Map<PropertyAccessor<Sequence, Object>, Column<SequenceTable, Object>> getPooledSequenceFieldMapping() {
return (Map) Maps
.forHashMap(PropertyAccessor.class, Column.class)
.add(Sequence.SEQUENCE_NAME_FIELD, sequenceNameColumn)
.add(Sequence.VALUE_FIELD, nextValColumn);
}
}
/**
* POJO which represents a line in the sequence table which is composed of 2 columns: one for the name of the sequence, the second for its value.
*/
public static class Sequence {
private static final PropertyAccessor<Sequence, String> SEQUENCE_NAME_FIELD;
private static final PropertyAccessor<Sequence, Long> VALUE_FIELD;
static {
SEQUENCE_NAME_FIELD = PropertyAccessor.fromMethodReference(Sequence::getSequenceName, Sequence::setSequenceName);
VALUE_FIELD = PropertyAccessor.fromMethodReference(Sequence::getStep, Sequence::setStep);
}
private String sequenceName;
private long step;
private Sequence() {
}
public Sequence(String sequenceName) {
this.sequenceName = sequenceName;
}
public Sequence(String sequenceName, long step) {
this.sequenceName = sequenceName;
this.step = step;
}
public long getStep() {
return step;
}
public void setStep(long step) {
this.step = step;
}
public String getSequenceName() {
return sequenceName;
}
public void setSequenceName(String sequenceName) {
this.sequenceName = sequenceName;
}
}
/**
* Class aimed at executing update operations of {@link Sequence}.
*/
private class SequenceBoundJdbcOperation implements SeparateTransactionExecutor.JdbcOperation {
private final String sequenceName;
private final int stepSize;
private Sequence sequence;
private SequenceBoundJdbcOperation(String sequenceName, int nextStepSize) {
this.sequenceName = sequenceName;
this.stepSize = nextStepSize;
}
@Override
public void execute(Connection currentSeparateConnection) {
sequence = readStep(sequenceName);
if (sequence != null) {
sequence.setStep(sequence.getStep() + stepSize);
updateById(sequence);
} else {
sequence = new Sequence(sequenceName);
sequence.setStep(stepSize);
insert(sequence);
}
}
private Sequence readStep(String sequenceName) {
return select(sequenceName);
}
public long getUpperBound() {
return sequence.getStep();
}
}
private static class SequencePersisterConfigurer {
private DefaultEntityMapping<Sequence, String, SequenceTable> buildConfiguration(PooledHiLoSequenceStorageOptions storageOptions) {
// Sequence table creation
SequenceTable sequenceTable = new SequenceTable(null, storageOptions.getTable(), storageOptions.getSequenceNameColumn(), storageOptions.getValueColumn());
// Strategy building
// NB: no id generator here because we manage ids (see reservePool)
return new DefaultEntityMapping<>(
Sequence.class,
sequenceTable,
sequenceTable.getPooledSequenceFieldMapping(),
Sequence.SEQUENCE_NAME_FIELD,
// Setting a "Noop" identifier manager because we use a insert(..), updateById(..) and select(..) for simple case
// (no complex graph nor chain of code on persisted instance)
new AlreadyAssignedIdentifierManager<>(String.class, c -> {}, c -> true));
}
}
}